首先先來定義一下什麼是資料庫正規化:
關聯式資料庫正規化的過程,其實就是將一些實體的描述資料,透過一定的程序,將表單簡化,直到一張表單只單純描述一個事實為止。
簡單來說,也就是透過一定的程序,去除資料庫中冗餘的內容,讓資料能夠井然有序且有效率的儲存。
經過正規化後的資料庫,應具備以下特性:
我們先來看一下這張完全未經過任何正規化的table:
orders
custmor_id | custmor_name | gender | items | prices | store_name | address | date | sex |
---|---|---|---|---|---|---|---|---|
1 | 阿寶 | 女 | 1 | 鉛筆, 橡皮擦 | 20, 50 | 1 | 久成久 | 東區大學路 |
2 | 豆芽 | 女 | 3 | 牛奶, 三明治 | 70, 10 | 3 | 全家 | 東區北門路 |
3 | 老皮 | 男 | 1 | 鉛筆, 牛奶 | 20, 70 | 1 | 久成久 | 東區大學路 |
很明顯的,有幾個大問題存在於這張表中:
以上這些問題都有可能提高重複資料及資料不一致問題發生的機率。
問題:
- 一個欄位儲存多筆資料,違反資料的原子性(atomicity)。
- 出現意義上重複的欄位。
- 缺乏主鍵(Primary Key)。
因此我們要透過第一正規化來修正以上的問題。
第一NF要完成的工作:
- 一個欄位只能有單一值
- 消除意義上重複的欄位
- 決定主鍵(必須是unique)
流程:
1.把原本一個欄位儲存多項資料的部份分開來儲存。
2.刪除意義重複的column。
3.設定一個primary key,作為一筆資料的唯一識別。
經過第一正規化後的表就會長成這樣,我們把每一項商品的消費都分開來儲存,然後把多餘的欄位刪掉。
現在這張表中的每個欄位都會是有意義的。
然後定義primary key為custmor_id和item_id組成複合主鍵。
orders
custmor_id | custmor_name | gender | item_id | item_name | price | count | total_amount | store_name | address | date |
---|---|---|---|---|---|---|---|---|---|---|
1 | 阿寶 | 女 | 1 | 鉛筆 | 20 | 1 | 1 | 20 | 久成久 | 東區大學路 |
1 | 阿寶 | 女 | 2 | 橡皮擦 | 50 | 2 | 2 | 100 | 久成久 | 東區大學路 |
2 | 豆芽 | 女 | 3 | 牛奶 | 70 | 3 | 2 | 210 | 全家 | 東區北門路 |
2 | 豆芽 | 女 | 4 | 三明治 | 10 | 4 | 1 | 40 | 全家 | 東區北門路 |
3 | 老皮 | 男 | 1 | 鉛筆 | 20 | 1 | 2 | 20 | 久成久 | 東區大學路 |
3 | 老皮 | 男 | 3 | 牛奶 | 70 | 2 | 1 | 140 | 全家 | 東區北門路 |
然而,看看這張經過第一正規化後的表,應該還是會覺得好像哪裡怪怪的,感覺很雜亂。
例如他的每一筆消費紀錄都要反覆紀錄消費者的性別、商品名稱、商店的名稱和地址,重複內容過多。
假設今天老皮在全家訂購了一百個三明治,那我就要重複輸入:姓名=>老皮,性別=>男性,商店的地址=>東區北門路。
又如果這間全家搬到了民族路上,那可能還需要在這一百筆資料裡一筆一筆做修改,非常沒有效率。
問題:
- 出現過多重複資料
因此我們要透過第二正規化來修正上述的問題
第二NF要完成的工作:
- 消除部分相依
部分相依的意思為跟主鍵只有一部份有關係,另一部份沒有關係的欄位,我們要把這些欄位獨立於另一張表。
在上述例子中,主鍵為customer_id和item_id,那像customer_name就只和customer_id相依、item_name只和item_id相依,這些都是應該被移出去到另一張表存放的欄位。
部分相依指通常發生在使用複合主鍵的情況下。如果一張表的主鍵由多個欄位組成,某些非主鍵欄位可能只依賴於主鍵中的一部分,這樣就會造成部分相依。
許多框架在建表時預設會以id作為pk,若該表已完成第一正規化,設定id為pk則直接滿足第二正規化,因為pk為單一鍵(已經是最小單位了,自然不會有和pk『部分相依』的情況)
流程:
第二正規化後,另外建了兩個獨立的表,items跟consumers來儲存消費者資訊和商品資訊。
orders分別和items跟customers關聯,把這些資料分出去儲存後,orders就單純的只儲存消費者和購買的商品,以及消費日期三種資料而已。
在orders中,消費者對應到customers的姓名,商品對應到items的名稱,而消費者和商品就是orders的foreign key。
customers
id | name | gender |
---|---|---|
1 | 阿寶 | 女 |
2 | 豆芽 | 女 |
3 | 老皮 | 男 |
stores
id | name | address |
---|---|---|
1 | 久成久 | 東區大學路 |
2 | 全家 | 東區北門路 |
items
id | name | price | store_id |
---|---|---|---|
1 | 鉛筆 | 20 | 1 |
2 | 橡皮擦 | 50 | 1 |
3 | 牛奶 | 70 | 2 |
4 | 三明治 | 10 | 2 |
orders
custmor_id | item_id | count | total_amount | date |
---|---|---|---|---|
1 | 1 | 1 | 20 | 12/17 |
1 | 2 | 2 | 100 | 12/17 |
2 | 3 | 3 | 210 | 12/18 |
2 | 4 | 1 | 40 | 12/18 |
3 | 1 | 4 | 20 | 12/19 |
3 | 3 | 1 | 140 | 12/19 |
然而,這樣還是會有一個問題,就是遞移關係。
所謂遞移關係,在這個範例裡就是指:total_amount是依賴price及count的資訊,而item_id和count又和主鍵直接相關,那total_amount和主鍵之間的關係就是遞移關係。
遞移相依:欄位1和主鍵相關,欄位2和欄位1相關,欄位2和主鍵就是遞移相依
候選鍵:欄位組合讓資料能是唯一的,並且是最小唯一
用個比較容易理解的方式來說明:為了避免count改變而total_amount沒改到造成資料錯誤,應該把total_amount這個從這張表中移除。
問題:存在主鍵以外的欄位與主鍵間接(遞移)相依
在第三正規化的規範中,要消除資料表中與主欄位的遞移相依
第三NF要完成的工作:
- 消除資料表中的遞移相依
customers
id | name | gender |
---|---|---|
1 | 阿寶 | 女 |
2 | 豆芽 | 女 |
3 | 老皮 | 男 |
stores
id | name | address |
---|---|---|
1 | 久成久 | 東區大學路 |
2 | 全家 | 東區北門路 |
items
id | name | price | store_id |
---|---|---|---|
1 | 鉛筆 | 20 | 1 |
2 | 橡皮擦 | 50 | 1 |
3 | 牛奶 | 70 | 2 |
4 | 三明治 | 10 | 2 |
orders
customer_id | item_id | count | date |
---|---|---|---|
1 | 1 | 1 | 12/17 |
1 | 2 | 2 | 12/17 |
2 | 3 | 3 | 12/18 |
2 | 4 | 1 | 12/18 |
3 | 1 | 4 | 12/19 |
3 | 3 | 1 | 12/19 |
但是,並不是每次建表都要照著正規化的規範才是最好的。
假如業務需求就單純是要很高頻率的去查詢某個消費者購買的商品名稱及商店地址的資料,就必須先從customers關聯到items,再關聯到stores。
這樣第二正規化之後所帶來的好處其實並不比他所帶來的開銷還要大,其實只要做到第一正規化就好了。
又或著,無論這個商品價格在消費者訂購之後進行幾次更改,需求就是要能查得到他購買當下的價格,那麼上述範例在遵循第二、第三正規化進行修正後,反而更不適用於這個情境。
因此,資料庫正規化並不是什麼完全不可違背的教條,要進行到什麼程度,應視對資料庫操作的需求和資料量而定,而不是每次建表都只要無腦的全部照著正規化的規範去設計你的資料表就好。